package groupManagementModule;

import generalObjects.BaseClientContract;
import generalObjects.GroupView;

import java.rmi.ConnectException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.ServerNotActiveException;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;

import nameServerPersistent.ServerRMI;

import communicationModule.MessageObject;

/**
 * An object of this class provides an implementation of a bully-like algorithm
 * used in group leader election. This class is used when the client detects
 * that the leader of the group has crashed. Which requires a new leader to be
 * elected within the group.
 */
public class BullyAlgorithm {

    private final Properties sessionProp;
    private final String[] nameservers;
    private final AtomicBoolean groupChanged;

    public BullyAlgorithm(final Properties sessionProp, String[] nameservers,
            AtomicBoolean groupChanged) {
        this.sessionProp = sessionProp;
        this.nameservers = nameservers;
        this.groupChanged = groupChanged;
    }

    public void startBully(final GroupView gView, final String myNick,
            final String oldLeader) throws RemoteException, NotBoundException,
            ServerNotActiveException {

        if (isLeader(gView, myNick, oldLeader)) {
            sendCoordinatorMsg(gView, myNick, oldLeader);
        } else {
            final GroupView higherIdentifiers = getClientsWithHigherIdentifiers(
                    gView, myNick, oldLeader);
            final BaseClientContract myStub = gView.getView().get(myNick);

            final MessageObject msg = new MessageObject(myNick, "election",
                    oldLeader, higherIdentifiers, "");

            // Sends election messages to clients with higher identifiers
            myStub.send(msg);

        }
    }

    public void sendCoordinatorMsg(final GroupView gView, final String myNick,
            final String oldLeader) throws RemoteException, NotBoundException,
            ServerNotActiveException {

        final BaseClientContract myStub = gView.getView().get(myNick);

        final MessageObject msg = new MessageObject(myNick, "coordinator",
                myNick, gView, "");

        // Sends coordinator messages to the group view
        myStub.send(msg);
        // Register the new leader at the naming server
        for (String addr : nameservers) {
            try {
                rebindLeader(addr, gView, myStub, oldLeader);
                return;
            } catch (RemoteException e) {
            }
        }
        throw new ConnectException(nameservers[nameservers.length - 1]);
    }

    private void rebindLeader(String addr, GroupView gView,
            BaseClientContract newLeaderStub, String oldLeader)
            throws RemoteException, ServerNotActiveException, NotBoundException {
        final Registry registry = LocateRegistry.getRegistry(addr);
        final ServerRMI stub = (ServerRMI) registry.lookup("Nameserver");
        stub.rebindLeader(sessionProp.getProperty("groupname"), newLeaderStub);
        gView.removeMember(oldLeader);
        groupChanged.compareAndSet(false, true);
    }

    private GroupView getClientsWithHigherIdentifiers(final GroupView gView,
            final String myNick, final String oldLeader) {

        final GroupView higherIdentifiers = new GroupView();
        final int myIdentifier = hashFunction(myNick);
        BaseClientContract stub;
        int seq;

        for (final String nick : gView.getView().keySet()) {
            if (nick.equals(myNick)) {
                continue;
            }
            if (nick.equals(oldLeader)) {
                continue;
            }
            if (myIdentifier < hashFunction(nick)) {
                stub = gView.getView().get(nick);
                seq = gView.getVectorClock().get(nick);
                higherIdentifiers.appendMember(nick, stub, seq);
            }
        }
        stub = gView.getView().get(myNick);
        seq = gView.getVectorClock().get(myNick);
        higherIdentifiers.appendMember(myNick, stub, seq);
        return higherIdentifiers;
    }

    private boolean isLeader(final GroupView gView, final String myNick,
            final String oldLeader) {

        final int myIdentifier = hashFunction(myNick);
        for (final String nick : gView.getView().keySet()) {
            if (nick.equals(myNick)) {
                continue;
            }
            if (nick.equals(oldLeader)) {
                continue;
            }
            if (myIdentifier < hashFunction(nick)) {
                return false;
            }
        }
        return true;
    }

    private int hashFunction(final String nick) {
        int hash = 7;
        for (int i = 0; i < nick.length(); i++) {
            hash = hash * 31 + nick.charAt(i);
        }
        return hash;
    }
}
